﻿//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// System.ServiceModel.Internals\System\Runtime\ActionItem.cs
//------------------------------------------------------------

#if DESKTOPCLR
using System;
using System.Diagnostics;
using System.Security;
using System.Threading;
using CuteAnt.Diagnostics;

namespace CuteAnt.Runtime
{
  public abstract class ActionItem
  {
    [Fx.Tag.SecurityNote(Critical = "Stores the security context, used later in binding back into")]
    [SecurityCritical]
    SecurityContext context;
    bool isScheduled;

    bool lowPriority;

    protected ActionItem()
    {
    }

    public bool LowPriority
    {
      get
      {
        return this.lowPriority;
      }
      protected set
      {
        this.lowPriority = value;
      }
    }

    public static void Schedule(Action<object> callback, object state)
    {
      Schedule(callback, state, false);
    }
    [Fx.Tag.SecurityNote(Critical = "Calls into critical method ScheduleCallback",
        Safe = "Schedule invoke of the given delegate under the current context")]
    [SecuritySafeCritical]
    public static void Schedule(Action<object> callback, object state, bool lowPriority)
    {
      Fx.Assert(callback != null, "A null callback was passed for Schedule!");

      if (PartialTrustHelpers.ShouldFlowSecurityContext ||
          WaitCallbackActionItem.ShouldUseActivity ||
          Fx.Trace.IsEnd2EndActivityTracingEnabled)
      {
        new DefaultActionItem(callback, state, lowPriority).Schedule();
      }
      else
      {
        ScheduleCallback(callback, state, lowPriority);
      }
    }

    [Fx.Tag.SecurityNote(Critical = "Called after applying the user context on the stack or (potentially) " +
        "without any user context on the stack")]
    [SecurityCritical]
    protected abstract void Invoke();

    [Fx.Tag.SecurityNote(Critical = "Access critical field context and critical property " +
        "CallbackHelper.InvokeWithContextCallback, calls into critical method " +
        "PartialTrustHelpers.CaptureSecurityContextNoIdentityFlow, calls into critical method ScheduleCallback; " +
        "since the invoked method and the capturing of the security contex are de-coupled, can't " +
        "be treated as safe")]
    [SecurityCritical]
    protected void Schedule()
    {
      if (isScheduled)
      {
        throw Fx.Exception.AsError(new InvalidOperationException(InternalSR.ActionItemIsAlreadyScheduled));
      }

      this.isScheduled = true;
      if (PartialTrustHelpers.ShouldFlowSecurityContext)
      {
        this.context = PartialTrustHelpers.CaptureSecurityContextNoIdentityFlow();
      }
      if (this.context != null)
      {
        ScheduleCallback(CallbackHelper.InvokeWithContextCallback);
      }
      else
      {
        ScheduleCallback(CallbackHelper.InvokeWithoutContextCallback);
      }
    }
    [Fx.Tag.SecurityNote(Critical = "Access critical field context and critical property " +
        "CallbackHelper.InvokeWithContextCallback, calls into critical method ScheduleCallback; " +
        "since nothing is known about the given context, can't be treated as safe")]
    [SecurityCritical]
    protected void ScheduleWithContext(SecurityContext context)
    {
      if (context == null)
      {
        throw Fx.Exception.ArgumentNull("context");
      }
      if (isScheduled)
      {
        throw Fx.Exception.AsError(new InvalidOperationException(InternalSR.ActionItemIsAlreadyScheduled));
      }

      this.isScheduled = true;
      this.context = context.CreateCopy();
      ScheduleCallback(CallbackHelper.InvokeWithContextCallback);
    }
    [Fx.Tag.SecurityNote(Critical = "Access critical property CallbackHelper.InvokeWithoutContextCallback, " +
        "Calls into critical method ScheduleCallback; not bound to a security context")]
    [SecurityCritical]
    protected void ScheduleWithoutContext()
    {
      if (isScheduled)
      {
        throw Fx.Exception.AsError(new InvalidOperationException(InternalSR.ActionItemIsAlreadyScheduled));
      }

      this.isScheduled = true;
      ScheduleCallback(CallbackHelper.InvokeWithoutContextCallback);
    }

    [Fx.Tag.SecurityNote(Critical = "Calls into critical methods IOThreadScheduler.ScheduleCallbackNoFlow, " +
        "IOThreadScheduler.ScheduleCallbackLowPriNoFlow")]
    [SecurityCritical]
    static void ScheduleCallback(Action<object> callback, object state, bool lowPriority)
    {
      Fx.Assert(callback != null, "Cannot schedule a null callback");
      if (lowPriority)
      {
        IOThreadScheduler.ScheduleCallbackLowPriNoFlow(callback, state);
      }
      else
      {
        IOThreadScheduler.ScheduleCallbackNoFlow(callback, state);
      }
    }

    [Fx.Tag.SecurityNote(Critical = "Extract the security context stored and reset the critical field")]
    [SecurityCritical]
    SecurityContext ExtractContext()
    {
      Fx.Assert(this.context != null, "Cannot bind to a null context; context should have been set by now");
      Fx.Assert(this.isScheduled, "Context is extracted only while the object is scheduled");
      SecurityContext result = this.context;
      this.context = null;
      return result;
    }

    [Fx.Tag.SecurityNote(Critical = "Calls into critical static method ScheduleCallback")]
    [SecurityCritical]
    void ScheduleCallback(Action<object> callback)
    {
      ScheduleCallback(callback, this, this.lowPriority);
    }

    [SecurityCritical]
    static class CallbackHelper
    {
      [Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
      static Action<object> invokeWithContextCallback;
      [Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
      static Action<object> invokeWithoutContextCallback;
      [Fx.Tag.SecurityNote(Critical = "Stores a delegate to a critical method")]
      static ContextCallback onContextAppliedCallback;
      [Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
          "a delegate to a critical method")]
      public static Action<object> InvokeWithContextCallback
      {
        get
        {
          if (invokeWithContextCallback == null)
          {
            invokeWithContextCallback = new Action<object>(InvokeWithContext);
          }
          return invokeWithContextCallback;
        }
      }

      [Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
          "a delegate to a critical method")]
      public static Action<object> InvokeWithoutContextCallback
      {
        get
        {
          if (invokeWithoutContextCallback == null)
          {
            invokeWithoutContextCallback = new Action<object>(InvokeWithoutContext);
          }
          return invokeWithoutContextCallback;
        }
      }
      [Fx.Tag.SecurityNote(Critical = "Provides access to a critical field; Initialize it with " +
          "a delegate to a critical method")]
      public static ContextCallback OnContextAppliedCallback
      {
        get
        {
          if (onContextAppliedCallback == null)
          {
            onContextAppliedCallback = new ContextCallback(OnContextApplied);
          }
          return onContextAppliedCallback;
        }
      }
      [Fx.Tag.SecurityNote(Critical = "Called by the scheduler without any user context on the stack")]
      static void InvokeWithContext(object state)
      {
        SecurityContext context = ((ActionItem)state).ExtractContext();
        SecurityContext.Run(context, OnContextAppliedCallback, state);
      }

      [Fx.Tag.SecurityNote(Critical = "Called by the scheduler without any user context on the stack")]
      static void InvokeWithoutContext(object state)
      {
        ((ActionItem)state).Invoke();
        ((ActionItem)state).isScheduled = false;
      }
      [Fx.Tag.SecurityNote(Critical = "Called after applying the user context on the stack")]
      static void OnContextApplied(object o)
      {
        ((ActionItem)o).Invoke();
        ((ActionItem)o).isScheduled = false;
      }
    }

    class DefaultActionItem : ActionItem
    {
      [Fx.Tag.SecurityNote(Critical = "Stores a delegate that will be called later, at a particular context")]
      [SecurityCritical]
      Action<object> callback;
      [Fx.Tag.SecurityNote(Critical = "Stores an object that will be passed to the delegate that will be " +
          "called later, at a particular context")]
      [SecurityCritical]
      object state;

      bool flowLegacyActivityId;
      Guid activityId;
      EventTraceActivity eventTraceActivity;

      [Fx.Tag.SecurityNote(Critical = "Access critical fields callback and state",
          Safe = "Doesn't leak information or resources")]
      [SecuritySafeCritical]
      public DefaultActionItem(Action<object> callback, object state, bool isLowPriority)
      {
        Fx.Assert(callback != null, "Shouldn't instantiate an object to wrap a null callback");
        base.LowPriority = isLowPriority;
        this.callback = callback;
        this.state = state;
        if (WaitCallbackActionItem.ShouldUseActivity)
        {
          this.flowLegacyActivityId = true;
          this.activityId = EtwDiagnosticTrace.ActivityId;
        }
        if (Fx.Trace.IsEnd2EndActivityTracingEnabled)
        {
          this.eventTraceActivity = EventTraceActivity.GetFromThreadOrCreate();
          if (TraceCore.ActionItemScheduledIsEnabled(Fx.Trace))
          {
            TraceCore.ActionItemScheduled(Fx.Trace, this.eventTraceActivity);
          }
        }

      }

      [Fx.Tag.SecurityNote(Critical = "Implements a the critical abstract ActionItem.Invoke method, " +
          "Access critical fields callback and state")]
      [SecurityCritical]
      protected override void Invoke()
      {
        if (this.flowLegacyActivityId || Fx.Trace.IsEnd2EndActivityTracingEnabled)
        {
          TraceAndInvoke();
        }
        else
        {
          this.callback(this.state);
        }
      }

      [Fx.Tag.SecurityNote(Critical = "Implements a the critical abstract Trace method, " +
              "Access critical fields callback and state")]
      [SecurityCritical]
      void TraceAndInvoke()
      {
        //
        if (this.flowLegacyActivityId)
        {
          Guid currentActivityId = EtwDiagnosticTrace.ActivityId;
          try
          {
            EtwDiagnosticTrace.ActivityId = this.activityId;
            this.callback(this.state);
          }
          finally
          {
            EtwDiagnosticTrace.ActivityId = currentActivityId;
          }
        }
        else
        {
          Guid previous = Guid.Empty;
          bool restoreActivityId = false;
          try
          {
            if (this.eventTraceActivity != null)
            {
              previous = Trace.CorrelationManager.ActivityId;
              restoreActivityId = true;
              Trace.CorrelationManager.ActivityId = this.eventTraceActivity.ActivityId;
              if (TraceCore.ActionItemCallbackInvokedIsEnabled(Fx.Trace))
              {
                TraceCore.ActionItemCallbackInvoked(Fx.Trace, this.eventTraceActivity);
              }
            }
            this.callback(this.state);
          }
          finally
          {
            if (restoreActivityId)
            {
              Trace.CorrelationManager.ActivityId = previous;
            }
          }
        }
      }
    }
  }
}
#endif
